/*******************************************************************************
 * Copyright  2015 rzorzorzo@users.sf.net
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *******************************************************************************/

package org.rzo.yajsw.script;

import groovy.lang.Binding;
import groovy.lang.GroovyClassLoader;
import groovy.lang.GroovyObject;
import io.netty.util.internal.logging.InternalLogger;

import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.vfs2.FileName;
import org.apache.commons.vfs2.FileObject;
import org.codehaus.groovy.control.CompilationFailedException;
import org.rzo.yajsw.boot.WrapperLoader;
import org.rzo.yajsw.util.VFSUtils;
import org.rzo.yajsw.wrapper.WrappedJavaProcess;
import org.rzo.yajsw.wrapper.WrappedProcess;

/**
 * The Class GroovyScript.
 */
public class GroovyScript extends AbstractScript
{

	public static Map context = Collections.synchronizedMap(new HashMap());
	/** The binding. */
	final Binding binding;

	final InternalLogger _logger;

	volatile GroovyObject _script;

	final boolean _reload;

	final String _encoding;

	/**
	 * Instantiates a new groovy script.
	 * 
	 * @param script
	 *            the script
	 * @param timeout
	 * @throws IOException
	 * @throws CompilationFailedException
	 * @throws IllegalAccessException
	 * @throws InstantiationException
	 * @throws ClassNotFoundException
	 */
	public GroovyScript(final String script, final String id,
			final WrappedProcess process, final String[] args,
			final int timeout, final InternalLogger logger, String encoding,
			boolean reload, int maxConcInvocations)
			throws CompilationFailedException, IOException,
			InstantiationException, IllegalAccessException,
			ClassNotFoundException
	{
		super(script, id, process, args, timeout, maxConcInvocations);
		_reload = reload;
		_encoding = encoding;

		// let's call some method on an instance
		_script = getScriptInstance(script, encoding);
		binding = (Binding) _script.invokeMethod("getBinding", null);
		binding.setVariable("args", args);
		binding.setVariable("callCount", 0);
		binding.setVariable("context", context);
		if (process != null && logger == null)
			_logger = process.getInternalWrapperLogger();
		else
			_logger = logger;
		binding.setVariable("logger", _logger);
	}

	private void setGroovyClasspath(GroovyClassLoader loader)
	{
		ArrayList cp = WrapperLoader.getGroovyClasspath();
		for (Iterator it = cp.listIterator(); it.hasNext();)
			loader.addURL((URL) it.next());
	}

	static GroovyClassLoader groovyClassLoader;

	private GroovyObject getScriptInstance(String scriptFileName,
			String encoding) throws IOException, InstantiationException,
			IllegalAccessException, ClassNotFoundException
	{
		FileObject fileObject = VFSUtils.resolveFile(".", scriptFileName);
		FileName fileName = fileObject.getName();
		long lastModified = fileObject.getContent().getLastModifiedTime();
		String scriptName = StringUtils.removeEnd(fileName.getBaseName(), "."
				+ fileName.getExtension())
				+ "_" + lastModified;

		synchronized (GroovyScript.class)
		{
			if (groovyClassLoader == null)
			{
				groovyClassLoader = new GroovyClassLoader(getClass()
						.getClassLoader());
				setGroovyClasspath(groovyClassLoader);
			}

			try
			{
				Class clazz = Class
						.forName(scriptName, true, groovyClassLoader);
				if (_script == null)
					return (GroovyObject) clazz.newInstance();
				else
					return _script;
			}
			catch (ClassNotFoundException e)
			{
				if (_script != null)
					log("script changed -> reloading");
				InputStream in = null;
				String scriptSrc = null;
				try
				{
					in = fileObject.getContent().getInputStream();
					if (encoding == null)
						scriptSrc = IOUtils.toString(in);
					else
						scriptSrc = IOUtils.toString(in, encoding);
				}
				finally
				{
					if (in != null)
						in.close();
				}
				return (GroovyObject) groovyClassLoader.parseClass(scriptSrc,
						scriptName + ".groovy").newInstance();
			}
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.rzo.yajsw.script.AbstractScript#execute(java.lang.String,
	 * java.lang.String, java.lang.String, java.lang.String, java.lang.String,
	 * java.lang.String, java.lang.Object)
	 */
	public Object execute(String line)
	{
		Object result = null;

		if (_script == null)
		{
			System.out.println("cannot execute script " + _name);
			return null;
		}
		if (_reload)
		{
			GroovyObject script = null;
			try
			{
				script = getScriptInstance(_name, _encoding);
			}
			catch (Exception e)
			{
				e.printStackTrace();
			}
			if (script != null)
			{
				if (_script != script)
				{
					script.invokeMethod("setBinding", binding);
					_script = script;
				}
			}
		}
		binding.setVariable("id", _id);
		if (_process != null)
		{
			binding.setVariable("state", _process.getStringState());
			binding.setVariable("count", _process.getRestartCount());
			binding.setVariable("pid", _process.getAppPid());
			binding.setVariable("exitCode", _process.getExitCode());
			binding.setVariable("line", line);
			binding.setVariable("process", _process);
		}
		try
		{
			synchronized (_script) 
			{
				result = _script.invokeMethod("run", new Object[] {});
			}
		}
		catch (Throwable e)
		{
			if (_logger != null)
				_logger.info("execption in script " + this._name, e);
			else
				e.printStackTrace();
		}
		binding.setVariable("callCount",
				((Integer) binding.getVariable("callCount")).intValue() + 1);
		return result;
	}

	public static void main(String[] args) throws Exception, IOException,
			InstantiationException, IllegalAccessException
	{
		WrappedJavaProcess w = new WrappedJavaProcess();
		w.getLocalConfiguration().setProperty("wrapper.config",
				"conf/wrapper.helloworld.conf");
		w.init();
		GroovyScript script = new GroovyScript("./scripts/timeCondition.gv",
				"id", w, new String[] { "11", "12" }, 0, null, null, false, 1);
		script.execute();
		script.execute();
		script = new GroovyScript("./scripts/fileCondition.gv", "id", w,
				new String[] { "anchor.lck" }, 0, null, null, false, 1);
		script.execute();
		script.execute();
		script = new GroovyScript("./scripts/snmpTrap.gv", "id", w,
				new String[] { "192.168.0.1", "1", "msg" }, 0, null, null,
				false, 1);
		script.execute();

	}

	public Object execute()
	{
		return execute("");
	}

	public void executeWithTimeout()
	{
		executeWithTimeout("");
	}

	public void interrupt()
	{
		if (_future != null)
			_future.cancel(true);
	}

	void log(String msg)
	{
		if (_logger != null)
			_logger.info(msg);
		else
			System.out.println(msg);
	}

	public Object invoke(String method, Object... x)
	{
		Object result = null;
		try
		{
			result = _script.invokeMethod(method, x);
		}
		catch (Exception ex)
		{
			ex.printStackTrace();
		}
		return result;
	}

}
